#@+leo-ver=4
#@+node:@file DiffTeXCore2.1.py
#@@language python
#@<<declarations>>
#@+node:<<declarations>>
#!/usr/bin/python2.2
"""LaTeX Diff core compents"""

__author__ = 'Ian Mulvany <ian@mulvany.net>'
__copyright__ = '(C) 2003 Ian Mulvany. GNU GPL 2.'
__version__ = '0.2'

import difflib
import string
import os
import copy
import re

#workingDir = "//Users//ianmulvany//code//python//MPython//springer//manuscriptParser//"
#workingDir = "e:\Dokumente und Einstellungen\Mulvany\Eigene Dateien\L_Leos\\parserInput\\"
#workingDir = "c:/Personal Documents And Files/python current/TexParser/parserInput/"
workingDir = "/Users/ian/Documents/t/TexTools/TexParser/parserInput/"
#@-node:<<declarations>>
#@nl
#@+others
#@+node:checkFences
"""
check that {,( and [ are balanced while ignoring 
any \{ etc.
"""

def checkFences(testString):
	skip = 0
	 
	curlyLeft = 0
	curlyRight = 0
	curlyEarly = 0
	squareLeft = 0
	squareRight = 0
	squareEarly = 0
	parenLeft = 0
	parenRight = 0
	parenEarly = 0
	
	for char in testString:
		if skip == 1:
			skip = 0
			continue
		elif char == "\\":
			skip = 1
			continue
		elif char == '{':
			curlyLeft += 1
		elif char == '}':
			curlyRight += 1
		elif char == '[':
			squareLeft += 1
		elif char == ']':
			squareRight += 1
		elif char == '(':
			parenLeft += 1
		elif char == ')':
			parenRight += 1
		else:
			continue
		
		if curlyLeft - curlyRight < 0:
			curlyEarly += 1
			
		if squareLeft - squareRight < 0:
			squareEarly += 1
			
		if parenLeft - parenRight < 0:
			parenEarly += 1
			
 
	if curlyLeft - curlyRight != 0:
		print " curly brackets are unbalenced "
			
	if squareLeft - squareRight != 0:
		print " square brackets are unbalenced "
		
	if parenLeft - parenRight != 0:
		print " parenntheses are unbalenced "

	if curlyEarly > 0:
		print " a closing curly bracket apprears before an opening one "	

	if squareEarly > 0:
		print " a closing square bracket apprears before an opening one "	

	if parenEarly > 0:
		print " a closing parenthesis apprears before an opening one "	
			
#@nonl
#@-node:checkFences
#@+node:getIFileNames
"""
This module will be replaced by a GUI later, perhaps early next week.
"""
def getFileNames(workingDir):
	
	fileList = os.listdir(workingDir)

	print '=' * 15
	print "here is the list of files to choose from"

	for x in range(len(fileList)):
		print x, '\t : \t' , fileList[x]
	print ''
			
	index1 = int(raw_input("please enter the number for the first file   :"))
	index2 = int(raw_input("please enter the number for the second file  :"))
	
	a = workingDir + fileList[index1]
	b = workingDir + fileList[index2]
	c = workingDir + "diffFile.tex"

	return a,b,c

#@-node:getIFileNames
#@+node:getIFileNames2

def getFileNames2(workingDir):
    """
    This module will be replaced by a GUI later, perhaps early next week.
    
    (or maybe it won't !)
    
    """
	
    #workingDir = "c:/Personal Documents And Files/python current/TexParser/parserInput/"
    
    a = workingDir + "file1.tex"
    b = workingDir + "file2.tex"
    c = workingDir + "diffFile.tex"

    return a,b,c
#@-node:getIFileNames2
#@+node:stripComment
"""
as argument takes a line
returns a line that does not have any comments
needs to ignore \% !
some \% and % the rest is a comment
"""
def stripComment(line):
	if line[0] == "%":
		return ' '
	elif "%" in line:
		laschar = ''
		for char in line:
			if char != "%":
				lastchar = char
				continue
			elif lastchar != '\\':
				code, comment = line.split("%",1)
				return code
			else:
				lastchar = char
				continue
	else:
		return line
#@-node:stripComment
#@+node:texDifforiginal
def texDifforiginal(a, b):
	"""Takes in lists a and b and returns a human-readable LaTeX diff. Perhaps"""

	out = []
	#a, b = tex2list(a), tex2list(b)
	s = difflib.SequenceMatcher(None, a, b)
	for e in s.get_opcodes():
		if e[0] == "replace":
			# @@ need to do something more complicated here
			# call textDiff but not for html, but for some html... ugh
			# gonna cop-out for now
			out.append('\Del{'+''.join(a[e[1]:e[2]]) + '}\Add{'+''.join(b[e[3]:e[4]])+"}")
		elif e[0] == "delete":
			out.append('\Del{'+ ''.join(a[e[1]:e[2]]) + "}")
		elif e[0] == "insert":
			out.append('\Add{'+''.join(b[e[3]:e[4]]) + "}")
		elif e[0] == "equal":
			out.append(''.join(b[e[3]:e[4]]))
		else: 
			raise "Um, something's broken. I didn't expect a '" + `e[0]` + "'."
	diffRatio = s.ratio()
	out.append("\n the diff ratio of this file is %f\n" % (diffRatio))
	return  out #''.join(out)
#@-node:texDifforiginal
#@+node:manageFile
def manageFile(filename):

    """

    since we do the same thing repetedly to
    two different files, tidy things up and put those
    functions in here.

    PP in the namespace here stands for Post Process

    """

    # read the file
    fileLines = readFileLines(filename)
    print fileLines
    print '-'*12
    print  'readFileLines :', [fileLines[-1][-5:]]
    print  "there are" , len(fileLines) , "lines in this input file"
    print '-'*12
    # pre process the file
    fileLinesPP = preprocessFileLines1(fileLines)
    print '-'*12
    print  'preprocessFileLines :', [fileLinesPP[-1]]
    print '-'*12
    # create a text stream
    textPP = joinLines2text(fileLinesPP,'')
    # create a list broken by whitespace
    ListPP = tex2list(textPP)
    print '-'*12
    print 'tex2list:', [ListPP[-1]]
    print '-'*12
    # get rid of multiple new lines?
    return ListPP
#@+node:readFileLines
def readFileLines(filename):
    
    """
    
    reads the lines of a file into a list
    
    """
    
    return open(filename,'U').readlines()
#@nonl
#@-node:readFileLines
#@+node:preprocessFileLines1
#@+others
#@+node:stripSafeNewLines
"""
Now we are looking at lines from a file that has been passed to us
from readlines() which means that the last character of every line is
garanteed to be a newline character. 

This makes detterming whether we should chop it or not very easy
"""
def stripSafeNewLines(line):
	#print len(line)
	#print line[-1]
	# if the line is only a newline character
	if len(line) < 2:
		return line
	# if the newline comes after a 'special' TeX command
	elif (line[-2] == '}') or (line[-2] == '\\') :
		return line
	# otherwise strip it	
	else: 
		# would be neater to use the re module here, but this works for now
		out = string.rstrip(line)
		out = out + ' '
		return out
#@-node:stripSafeNewLines
#@+node:doRexp
"""
This is a general regular expression module,
we pass the patterns and the line to the module

([ \t\r\f\v]+)		any whitespace that does not include a newline character! 
"""
def doRexp(pattern, replace, line):
	if re.search(pattern, line):
		out = re.sub(pattern, replace, line)
		return out
	else: 
		return line
#@nonl
#@-node:doRexp
#@-others

def preprocessFileLines1(fileLines):
	
	"""
	THINK VERY CAEFULLY ABOUT THESE REGEXPS THINK THINK
	
	things to consider+
	
	some change

	need to remove spaces from \begin{ command } and \end{ command } structures
	need to ensure that \Add and \Del{} commands do not contain curly brackets
	or other special characters.
	
	if an _ or a ^ are not followed by encasing brackets, then place encasing brackets areound their 
	arguments. Do this before we add out magi spaces.
	"""
	
	procList = []
	#
	# set up our 're' patterns that will be used in some of our functions in this module
	# many of these regex's were taken straigt from the program by Dekel Tsur.
	#
	# for cleaning up math modes
	underscore = r"(?<!(\\))(_(([[(?*.+!\\\w]*)(\s|\$)))"
	underscoreReplace = "^{\\4}\\5"
	power = r"(?<!(\\))(\^(([[(?*.+!\\\w]*)(\s|\$)))"
	powerReplace = "^{\\4}\\5"
	## any whitespace that does not include a newline character! 
	multiSpace = r"([ \t\r\f\v]+)"		
	space = " "
	#
	# }'s
	rCurly = r"(?<!\\)(})"
	rCurlyBlank = r" } "
	#
	# {'s
	lCurly = r"{"
	lBlankCurly = r"{ "
	#
	# both { and }, not currently used
	Curly = r"(?<!\\)(\{|\})"
	#
	# change totally blank lines to only a newline character
	balnkLine = r"^[\s]*(?![\s*\S])"
	newLine = r"\n"
	#
	# \command{text
	TeXCommand = r"(\\(\w+){)"
	#
	# a $ that does not have '\' or '$' in front and '$' behind 
	singleDollar = r"(?<!(\\|\$))(\$)(?!\$)"
	singleDollarSpace = r" $ "
	#
	# $$ without "\" in front
	mathmode = r"(?<!\\)(\$\$)"
	#
	# item
	item = r"(\\(item))"	
	#
	# ( and )
	Parenthesis = r"(\(|\)|\(|\)|\[|\])"
	#
	# remove some spaces that could cause problems
	badSpaces = r"\\(begin|end|label|ref|cite|text|textrm|mbox|usepackage|thispagestyle){ (\w+)\*? }" 
	badSpacesReplace = r"\\\1{\2}"
	#
	# some spaces to place place as replacements
	newlineSub = "%\n\\1"
	newlineMathSub = "%\n \\1 "
	subSpace = r" \1 "


	for line in fileLines:
		# remove comments
		#print [line]
		#curLine = stripComment(line)
		#
		curLine = line
		# strip whitespace off the start of the line
		if curLine != None:
			curLine = string.lstrip(curLine)
		
		# strip 'safe' newline characters from the line
		curLine = stripSafeNewLines(curLine)
		#
		# replace a line(or multiple lines) that is(are) only whitespace with a newline
		curLine = doRexp(balnkLine,newLine,curLine)
		#
		# replace multiple spaces that do not have a newline character with one space
		curLine = doRexp(multiSpace,space,curLine)	
		#
		curLine = doRexp(underscore,underscoreReplace,curLine)
		curLine = doRexp(power,powerReplace,curLine)
		# add special padding around curly brackets, and perhaps other objects too
		#curLine = doRexp(Curly,subSpace,curLine)
		curLine = doRexp(rCurly,rCurlyBlank,curLine)
		curLine = doRexp(lCurly,lBlankCurly,curLine)
		curLine = doRexp(Parenthesis,subSpace,curLine)
		#
		# now add a newline before commands
		curLine = doRexp(TeXCommand,newlineSub,curLine)
		curLine = doRexp(item,newlineSub,curLine)	
		#
		# some more maths, dealing with single dollar signs and double dollar sighns
		curLine = doRexp(mathmode,newlineMathSub,curLine)	
		curLine = doRexp(singleDollar,singleDollarSpace,curLine)	
		#
		# take out some spaces that we put in that cause problems
		curLine = doRexp(badSpaces,badSpacesReplace,curLine)	
		
		#
		procList.append(curLine)
	return procList
#@-node:preprocessFileLines1
#@+node:tex2list
"""
This function will become my tex parser, oh yes it will.

This stripped down parser just splits our input file (a full text string) into a 
list sequence broken at whitespace whiltespace.

we might want to handle whitespace a bit more intelligently.
"""
def tex2list(x):
	cur = ''
	out = []
	for c in x:
		if c in string.whitespace: out.append(cur+c); cur = ''
		else: cur += c
	out.append(cur)
	return filter(lambda x: x is not '', out)
#@-node:tex2list
#@-node:manageFile
#@+node:texDiff
def texDiff(a, b):
	"""Takes in lists a and b and returns a human-readable LaTeX diff. Perhaps"""
	print 'in TeX Diff'
	textout = []
	
	diffout = []
	#a, b = tex2list(a), tex2list(b)
	s = difflib.SequenceMatcher(None, a, b)
	for e in s.get_opcodes():
		if e[0] == "replace":
			# @@ need to do something more complicated here
			# call textDiff but not for html, but for some html... ugh
			# gonna cop-out for now
			print 'deleting ', ''.join(a[e[1]:e[2]]), 'and adding ', ''.join(b[e[3]:e[4]])
			textout.append('\Del{'+''.join(a[e[1]:e[2]]) + '}\Add{'+''.join(b[e[3]:e[4]])+"}")
		elif e[0] == "delete":
			print 'deleting ', ''.join(a[e[1]:e[2]])
			textout.append('\Del{'+ ''.join(a[e[1]:e[2]]) + "}")
		elif e[0] == "insert":
			print 'adding ', ''.join(b[e[3]:e[4]])
			textout.append('\Add{'+''.join(b[e[3]:e[4]]) + "}")
		elif e[0] == "equal":
			textout.append(''.join(b[e[3]:e[4]]))
		else: 
			raise "Um, something's broken. I didn't expect a '" + `e[0]` + "'."
	diffRatio = s.ratio()
	textout.append("\n the diff ratio of this file is %f\n" % (diffRatio))
	return  textout #''.join(out)

#@-node:texDiff
#@+node:joinLines2text
"""
Lines is a list of lines, actually any list
joiner is a string that we use as glue between the items in the list
"""
def joinLines2text(Lines,joiner):
	return joiner.join(Lines)
#@nonl
#@-node:joinLines2text
#@+node:writeiFleLines
def writeiFleLines(textToWrite):
    
    """
    writes output    
    """
    
    fout = open(c,'w')
    fout.write(textToWrite)
    fout.close()
#@nonl
#@-node:writeiFleLines
#@-others
# read in the file names

a,b,c = getFileNames2(workingDir)

print a
print b
print c

# read the files
# preprocess the files
# join the files
# make wspc seperated lists from the files


aListPP = manageFile(a)
bListPP = manageFile(b)

#print aListPP

#print aListPP[-10:]
DiffList = texDiff(aListPP,bListPP)
DiffText = joinLines2text(DiffList,'')


writeiFleLines(DiffText)

#@-node:@file DiffTeXCore2.1.py
#@-leo
